﻿namespace wToggl
{
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Globalization;
    using System.IO;
    using System.Linq;
    using System.Runtime.CompilerServices;
    using System.Threading.Tasks;

    using Windows.ApplicationModel.Email;
    using Windows.Storage.Streams;
    using Windows.UI.Core;
    using Windows.UI.Xaml;
    using Windows.UI.Xaml.Controls;
    using Windows.UI.Xaml.Input;
    using Windows.UI.Xaml.Navigation;

    using OxyPlot;
    using OxyPlot.Axes;
    using OxyPlot.Series;

    using wToggl.Common;

    using wToggle.Platform;
    using wToggle.Platform.Annotations;
    using wToggle.Platform.Data;
    using wToggle.Platform.Interfaces;

    using XPlatUtils;

    using FontWeights = Windows.UI.Text.FontWeights;

    /// <summary>
    /// An empty page that can be used on its own or navigated to within a Frame.
    /// </summary>
    public sealed partial class ReportsPage : INotifyPropertyChanged
    {
        private readonly NavigationHelper navigationHelper;
        private readonly ObservableDictionary defaultViewModel = new ObservableDictionary();

        private Workspace workspace;

        private IEnumerable selectedClients;

        private IEnumerable selectedTags;

        private IEnumerable selectedProjects;

        private DateTimeOffset startDate = DateTime.Now.Date.AddDays(-6);

        private DateTimeOffset endDate = DateTime.Now.Date;

        private bool isWorking;

        private string overlayMessage;

        private ICollection<ReportTimeEntry> entries;

        private string workTotal;

        private PlotModel projectData;

        private PlotModel clientData;

        private int chartIndex;

        private PlotModel dateData;

        private DateTime dateMaximum;

        private DateTime dateMinimum;

        private string selectedWorkTitle;

        private string selectedWorkTime;

        private string selectedWorkPortion;

        private DateTimeAxis dateChartXAxis;

        public ReportsPage()
        {
            this.InitializeComponent();

            this.navigationHelper = new NavigationHelper(this);
            this.navigationHelper.LoadState += this.NavigationHelper_LoadState;
            this.navigationHelper.SaveState += this.NavigationHelper_SaveState;
        }

        public Workspace Workspace
        {
            get
            {
                return this.workspace;
            }

            set
            {
                this.workspace = value;
                this.OnPropertyChanged();
            }
        }

        /// <summary>
        /// Gets the <see cref="NavigationHelper"/> associated with this <see cref="Page"/>.
        /// </summary>
        public NavigationHelper NavigationHelper
        {
            get { return this.navigationHelper; }
        }

        /// <summary>
        /// Gets the view model for this <see cref="Page"/>.
        /// This can be changed to a strongly typed view model.
        /// </summary>
        public ObservableDictionary DefaultViewModel
        {
            get { return this.defaultViewModel; }
        }

        public IEnumerable SelectedClients
        {
            get
            {
                return this.selectedClients;
            }
            set
            {
                this.selectedClients = value;
                this.OnPropertyChanged();
            }
        }

        public IEnumerable SelectedTags
        {
            get
            {
                return this.selectedTags;
            }

            set
            {
                this.selectedTags = value;
                this.OnPropertyChanged();
            }
        }

        public IEnumerable SelectedProjects
        {
            get
            {
                return this.selectedProjects;
            }

            set
            {
                this.selectedProjects = value;
                this.OnPropertyChanged();
            }
        }

        public DateTimeOffset StartDate
        {
            get
            {
                return this.startDate;
            }
            set
            {
                this.startDate = value;
                this.OnPropertyChanged();
            }
        }

        public DateTimeOffset EndDate
        {
            get
            {
                return this.endDate;
            }
            set
            {
                this.endDate = value;
                this.OnPropertyChanged();
            }
        }

        public bool IsWorking
        {
            get
            {
                return this.isWorking;
            }
            set
            {
                this.isWorking = value;
                this.OnPropertyChanged();
                this.BottomAppBar.IsEnabled = !value;
            }
        }

        public string OverlayMessage
        {
            get
            {
                return this.overlayMessage;
            }
            set
            {
                this.overlayMessage = value;
                this.OnPropertyChanged();
            }
        }

        /// <summary>
        /// Populates the page with content passed during navigation.  Any saved state is also
        /// provided when recreating a page from a prior session.
        /// </summary>
        /// <param name="sender">
        /// The source of the event; typically <see cref="NavigationHelper"/>
        /// </param>
        /// <param name="e">Event data that provides both the navigation parameter passed to
        /// <see cref="Frame.Navigate(Type, Object)"/> when this page was initially requested and
        /// a dictionary of state preserved by this page during an earlier
        /// session.  The state will be null the first time a page is visited.</param>
        private async void NavigationHelper_LoadState(object sender, LoadStateEventArgs e)
        {
            try
            {
                this.IsWorking = true;
                this.OverlayMessage = "Loading data...";
                if (e.NavigationParameter == null)
                {
                    return;
                }

                var wid = (ulong)e.NavigationParameter;
                await this.LoadWorkspace(wid);

                if (!Toggly.HasInternetAccess())
                {
                    await
                        this.Dispatcher.ShowMessage(
                            "No internet connection detected, Reporting will not work unless you reconnect to the internet.",
                            "Toggly");
                }
               
                if (e.PageState == null || this.Workspace == null)
                {
                    return;
                }

                if (e.PageState.ContainsKey("start"))
                {
                    this.StartDate = (DateTime)e.PageState["start"];
                }

                if (e.PageState.ContainsKey("end"))
                {
                    this.EndDate = (DateTime)e.PageState["end"];
                }

                if (e.PageState.ContainsKey("projects"))
                {
                    var projectIds = ((string)e.PageState["projects"]).Split(',').Select(ulong.Parse);
                    var projects = this.Workspace.Projects.Where(p => projectIds.Contains(p.Id));
                    this.SelectedProjects = projects;
                }

                if (e.PageState.ContainsKey("clients"))
                {
                    var clientIds = ((string)e.PageState["clients"]).Split(',').Select(ulong.Parse);
                    var projects = this.Workspace.Clients.Where(c => clientIds.Contains(c.Id));
                    this.SelectedClients = projects;
                }

                if (e.PageState.ContainsKey("tags"))
                {
                    var tagIds = ((string)e.PageState["tags"]).Split(',').Select(ulong.Parse);
                    var projects = this.Workspace.Tags.Where(t => tagIds.Contains(t.Id));
                    this.SelectedTags = projects;
                }
            }
            finally
            {
                this.IsWorking = false;
            }
        }

        private async Task LoadWorkspace(ulong wid)
        {
            this.OverlayMessage = "Loading data...";
            this.IsWorking = true;

            var provider = ServiceContainer.Resolve<IWorkspaceProvider>();
            var settings = ServiceContainer.Resolve<IApplicationSettings>();

            try
            {
                var selectedWorkspace = await provider.GetWorkspace(wid);

                if (selectedWorkspace != null)
                {
                    settings.LastWorkspace = selectedWorkspace.Id;
                    await selectedWorkspace.LoadWorkspaceData();
                    this.Workspace = selectedWorkspace;
                    await this.OnPropertyChangedAsync("Projects");
                }
                else
                {
                    this.Workspace = null;
                    this.SelectedTags = null;
                    this.SelectedProjects = null;

                    await
                        this.Dispatcher.ShowMessage(
                            "No workspaces were found. Check your internet connectivity and try again.",
                            "Toggly");

                    this.Frame.GoBack();
                }
            }
            catch (InvalidOperationException e)
            {
                this.Frame.GoBack();
            }
        }

        /// <summary>
        /// Preserves state associated with this page in case the application is suspended or the
        /// page is discarded from the navigation cache.  Values must conform to the serialization
        /// requirements of <see cref="SuspensionManager.SessionState"/>.
        /// </summary>
        /// <param name="sender">The source of the event; typically <see cref="NavigationHelper"/></param>
        /// <param name="e">Event data that provides an empty dictionary to be populated with
        /// serializable state.</param>
        private void NavigationHelper_SaveState(object sender, SaveStateEventArgs e)
        {
            e.PageState["start"] = this.StartDate.Date;
            e.PageState["end"] = this.EndDate.Date;

            if (this.SelectedProjects != null)
            {
                var p = this.SelectedProjects.Cast<Project>().ToList();

                if (p.Any())
                {
                    e.PageState["projects"] = string.Join(",", p.Select(_ => _.Id));
                }
            }

            if (this.SelectedClients != null)
            {
                var c = this.SelectedClients.Cast<Client>().ToList();

                if (c.Any())
                {
                    e.PageState["clients"] = string.Join(",", c.Select(_ => _.Id));
                }
            }

            if (this.SelectedTags != null)
            {
                var t = this.SelectedTags.Cast<Tag>().ToList();

                if (t.Any())
                {
                    e.PageState["tags"] = string.Join(",", t.Select(_ => _.Id));
                }
            }
        }

        #region NavigationHelper registration

        /// <summary>
        /// The methods provided in this section are simply used to allow
        /// NavigationHelper to respond to the page's navigation methods.
        /// <para>
        /// Page specific logic should be placed in event handlers for the  
        /// <see cref="NavigationHelper.LoadState"/>
        /// and <see cref="NavigationHelper.SaveState"/>.
        /// The navigation parameter is available in the LoadState method 
        /// in addition to page state preserved during an earlier session.
        /// </para>
        /// </summary>
        /// <param name="e">Provides data for navigation methods and event
        /// handlers that cannot cancel the navigation request.</param>
        protected override void OnNavigatedTo(NavigationEventArgs e)
        {
            Analytics.ReportPageVisit(this);  
            this.navigationHelper.OnNavigatedTo(e);
        }

        protected override void OnNavigatedFrom(NavigationEventArgs e)
        {
            this.navigationHelper.OnNavigatedFrom(e);
        }

        #endregion

        public event PropertyChangedEventHandler PropertyChanged;

        [NotifyPropertyChangedInvocator]
        private void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            var handler = this.PropertyChanged;
            if (handler != null)
            {
                handler(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        private async Task OnPropertyChangedAsync(string propertyName = null)
        {
            await this.Dispatcher.RunAsync(
                CoreDispatcherPriority.Normal,
                () =>
                {
                    var handler = this.PropertyChanged;
                    if (handler != null)
                    {
                        handler(this, new PropertyChangedEventArgs(propertyName));
                    }
                });
        }

        private async void SearchButton_OnClick(object sender, RoutedEventArgs e)
        {
            try
            {
                this.IsWorking = true;
                this.OverlayMessage = "Seaching for entries...";
                var provider = ServiceContainer.Resolve<ITimeEntryProvider>();

                var collection =
                    await
                    provider.FilterEntries(
                        this.Workspace.Id,
                        this.StartDate.Date,
                        this.EndDate.Date.AddDays(1),
                        (this.SelectedClients ?? new List<Client>()).Cast<Client>().Select(c => c.Id).ToList(),
                        (this.SelectedProjects ?? new List<Project>()).Cast<Project>().Select(p => p.Id).ToList(),
                        (this.SelectedTags ?? new List<Tag>()).Cast<Tag>().Select(t => t.Id).ToList());

                foreach (var t in collection)
                {
                    await t.ExpandProject();

                    if (t.Project != null)
                    {
                        await t.Project.LoadClient();
                    }
                }

                this.Entries = collection.OrderByDescending(t => t.Start).ToList();

                if (this.Entries != null)
                {
                    this.WorkTotal = TimeSpan.FromMilliseconds(this.Entries.Sum(t => t.Duration)).ToDurationString();
                    this.DateMinimum = this.StartDate.Date.AddDays(-1);
                    this.DateMaximum = this.EndDate.Date.AddDays(1);

                    this.ChartByProject(this.Entries);
                    this.ChartByClient(this.Entries);
                    this.ChartByDate(this.Entries);
                }
            }
            finally
            {
                this.IsWorking = false;
            }
        }

        public DateTime DateMaximum
        {
            get
            {
                return this.dateMaximum;
            }
            set
            {
                this.dateMaximum = value;
                this.OnPropertyChanged();
            }
        }

        public DateTime DateMinimum
        {
            get
            {
                return this.dateMinimum;
            }
            set
            {
                this.dateMinimum = value;
                this.OnPropertyChanged();
            }
        }

        private void ChartByProject(ICollection<ReportTimeEntry> timeEntries)
        {
            var noProject = timeEntries.Where(t => t.Project == null).ToList();
            var withProject = timeEntries.Where(t => t.Project != null).GroupBy(t => t.Project.Id);

            if (noProject.Any())
            {
                var model = new PlotModel { Title = "Time by project" };
                var pieSeries = new PieSeries();

                var data =
                    withProject.Select(
                        g => new NameValueItem { Name = g.First().Project.Name, Value = g.Sum(t => t.Duration) })
                        .Union(
                            new[]
                                { new NameValueItem { Name = "(no project)", Value = noProject.Sum(t => t.Duration) } });

                foreach (var item in data)
                {
                    pieSeries.Slices.Add(new PieSlice(item.Name + "\n" + TimeSpan.FromMilliseconds(item.Value).ToDurationString(), item.Value));
                }

                model.Series.Add(pieSeries);

                this.ProjectData = model;
            }
            else
            {

                var model = new PlotModel { Title = "Time by project" };
                var pieSeries = new PieSeries();
                var data =
                    withProject.Select(
                        g => new NameValueItem { Name = g.First().Project.Name, Value = g.Sum(t => t.Duration) });

                foreach (var item in data)
                {
                    pieSeries.Slices.Add(new PieSlice(item.Name + "\n" + TimeSpan.FromMilliseconds(item.Value).ToDurationString(), item.Value));
                }

                model.Series.Add(pieSeries);

                this.ProjectData = model;
            }
        }

        private void ChartByClient(ICollection<ReportTimeEntry> timeEntries)
        {
            var noProject =
                timeEntries.Where(t => t.Project == null)
                    .Union(timeEntries.Where(t => t.Project != null && t.Project.Client == null))
                    .ToList();

            var withClient = timeEntries.Where(t => t.Project != null && t.Project.Client != null).GroupBy(t => t.Project.Client.Id);

            var data =
                withClient.Select(
                    g => new NameValueItem { Name = g.First().Project.Client.Name, Value = g.Sum(t => t.Duration) })
                    .ToList();

            if (noProject.Any())
            {
                data.Add(new NameValueItem { Name = "(no client)", Value = noProject.Sum(t => t.Duration) });
            }

            var model = new PlotModel { Title = "Time by client" };
            var pieSeries = new PieSeries();

            foreach (var item in data)
            {
                pieSeries.Slices.Add(new PieSlice(item.Name + "\n" + TimeSpan.FromMilliseconds(item.Value).ToDurationString(), item.Value));
            }

            model.Series.Add(pieSeries);

            this.ClientData = model;
        }

        private void ChartByDate(ICollection<ReportTimeEntry> timeEntries)
        {
            var data =
                timeEntries.Where(t => t.Start != null)
                    .GroupBy(t => t.Start.Value.Date)
                    .Select(
                        g =>
                        new DateValueItem
                            {
                                Date = g.Key.Date,
                                Value = TimeSpan.FromMilliseconds(g.Sum(t => t.Duration)).TotalHours
                            })
                    .ToDictionary(t => t.Date, t => t.Value);

            var model = new PlotModel { Title = "Time by date", IsLegendVisible = false };

            var diff = (int)this.EndDate.Subtract(this.StartDate).TotalDays;

            var xAxis = new CategoryAxis
                            {
                                Position = AxisPosition.Bottom,
                                StringFormat = DateTimeFormatInfo.CurrentInfo.ShortDatePattern,
                                Title = "Date",
                                MajorGridlineStyle = LineStyle.None,
                                MinorGridlineStyle = LineStyle.None,
                                IsTickCentered = true,
                                MinorStep = 0
                            };

            if (diff < 7)
            {
                xAxis.MajorStep = 1;
            }
            else if (diff < 10)
            {
                xAxis.MajorStep = 3;
            }
            else if (diff < 14)
            {
                xAxis.MajorStep = 5;
            }
            else
            {
                xAxis.MajorStep = 7;
            }

            model.Axes.Add(xAxis);

            var linearAxis1 = new LinearAxis { AbsoluteMinimum = 0, MaximumPadding = 0.06, MinimumPadding = 0 };
            model.Axes.Add(linearAxis1);

            for (var i = 0; i < diff; i++)
            {
                var date = this.StartDate.Date.AddDays(i);
                var label = date.ToString(DateTimeFormatInfo.CurrentInfo.ShortDatePattern);
                xAxis.Labels.Add(label);
            }

            var columnSeries = new ColumnSeries { StrokeThickness = 0, Title = "Work hours" };

            for (var i = 0; i < diff; i++)
            {
                var date = this.StartDate.Date.AddDays(i);
                columnSeries.Items.Add(data.ContainsKey(date) ? new ColumnItem(data[date]) : new ColumnItem(0));
            }

            model.Series.Add(columnSeries);
            this.DateData = model;       
        }

        public ICollection<ReportTimeEntry> Entries
        {
            get
            {
                return this.entries;
            }
            set
            {
                this.entries = value.Any() ? value : null;

                this.OnPropertyChanged();
            }
        }

        public string WorkTotal
        {
            get
            {
                return this.workTotal;
            }
            set
            {
                this.workTotal = value;
                this.OnPropertyChanged();
            }
        }

        public PlotModel ProjectData
        {
            get
            {
                return this.projectData;
            }

            set
            {
                this.projectData = value;
                this.OnPropertyChanged();
            }
        }

        public PlotModel ClientData
        {
            get
            {
                return this.clientData;
            }

            set
            {
                this.clientData = value;
                this.OnPropertyChanged();
            }
        }

        public PlotModel DateData
        {
            get
            {
                return this.dateData;
            }

            set
            {
                this.dateData = value;
                this.OnPropertyChanged();
            }
        }

        public int ChartIndex
        {
            get
            {
                return this.chartIndex;
            }
            set
            {
                this.chartIndex = value;
                this.OnPropertyChanged();
            }
        }

        public string SelectedWorkTitle
        {
            get
            {
                return this.selectedWorkTitle;
            }
            set
            {
                this.selectedWorkTitle = value;
                this.OnPropertyChanged();
            }
        }

        public string SelectedWorkPortion
        {
            get
            {
                return this.selectedWorkPortion;
            }
            set
            {
                this.selectedWorkPortion = value;
                this.OnPropertyChanged();
            }
        }

        public string SelectedWorkTime
        {
            get
            {
                return this.selectedWorkTime;
            }
            set
            {
                this.selectedWorkTime = value;
                this.OnPropertyChanged();
            }
        }

        //public LinearAxis DateChartYAxis
        //{
        //    get
        //    {
        //        return new LinearAxis
        //                   {
        //                       Minimum = 0,
        //                       Orientation = AxisOrientation.Y,
        //                       Location = AxisLocation.Left,
        //                       Interval = 1
        //                   };
        //    }
        //}

        public DateTimeAxis DateChartXAxis
        {
            get
            {
                return this.dateChartXAxis;
            }

            private set
            {
                this.dateChartXAxis = value;
                this.OnPropertyChanged();
            }
        }

        private void ProjectChart_OnTapped(object sender, TappedRoutedEventArgs e)
        {
            this.ChartIndex = 0;
        }

        private void Selector_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            var grid = this.LayoutRoot.FindChildren<Grid>().FirstOrDefault(g => g.Name == "ChartNavigationGrid");

            if (grid == null)
            {
                return;
            }

            var blocks = grid.FindChildren<TextBlock>();

            foreach (var b in blocks)
            {
                b.FontWeight = FontWeights.Normal;
                b.FontSize = 18;
            }

            var element = blocks.ElementAt(this.ChartIndex);
            element.FontWeight = FontWeights.Bold;
            element.FontSize = 25;
        }

        private void ClientChart_OnTapped(object sender, TappedRoutedEventArgs e)
        {
            this.ChartIndex = 1;
        }

        private void DateChart_OnTapped(object sender, TappedRoutedEventArgs e)
        {
            this.ChartIndex = 2;
        }

        private async void PdfButton_OnClick(object sender, RoutedEventArgs e)
        {
            try
            {
                this.IsWorking = true;
                this.OverlayMessage = "Generating report...";
                var data = await this.GetPdfData();

                if (data == null)
                {
                    await this.Dispatcher.ShowMessage("Server returned no data.", "Toggly");
                    return;
                }

                var files = await Windows.Storage.ApplicationData.Current.LocalCacheFolder.GetFilesAsync();
                foreach (var storageFile in files)
                {
                    await storageFile.DeleteAsync();
                }

                var tempFile = await Windows.Storage.ApplicationData.Current.LocalCacheFolder.CreateFileAsync(
                    "report" + DateTime.UtcNow.ToFileTime() + ".pdf");

                using (var writer = await tempFile.OpenStreamForWriteAsync())
                {
                    await writer.WriteAsync(data, 0, data.Length);
                }

                var options = new Windows.System.LauncherOptions { DisplayApplicationPicker = true };
                await Windows.System.Launcher.LaunchFileAsync(tempFile, options);
            }
            finally
            {
                this.IsWorking = false;
            }
        }

        private async Task<byte[]> GetPdfData()
        {
            var provider = ServiceContainer.Resolve<ITimeEntryProvider>();

            var data =
                await
                provider.PdfReport(
                    this.Workspace.Id,
                    this.StartDate.Date,
                    this.EndDate.Date.AddDays(1).AddSeconds(-1),
                    (this.SelectedClients ?? new List<Client>()).Cast<Client>().Select(c => c.Id).ToList(),
                    (this.SelectedProjects ?? new List<Project>()).Cast<Project>().Select(p => p.Id).ToList(),
                    (this.SelectedTags ?? new List<Tag>()).Cast<Tag>().Select(t => t.Id).ToList());

            return data;
        }

        private async void SendPdfButton_OnClick(object sender, RoutedEventArgs e)
        {
            var mail = new EmailMessage { Subject = "Toggl report" };

            var data = await this.GetPdfData();

            if (data == null)
            {
                await this.Dispatcher.ShowMessage("Server returned no data.", "Toggly");
                return;
            }

            var files = await Windows.Storage.ApplicationData.Current.LocalCacheFolder.GetFilesAsync();
            foreach (var storageFile in files)
            {
                await storageFile.DeleteAsync();
            }

            var tempFile =
                await
                Windows.Storage.ApplicationData.Current.LocalCacheFolder.CreateFileAsync(
                    "Report.pdf");

            using (var writer = await tempFile.OpenStreamForWriteAsync())
            {
                await writer.WriteAsync(data, 0, data.Length);
            }

            var stream = RandomAccessStreamReference.CreateFromFile(tempFile);
            mail.Attachments.Add(new EmailAttachment("Report.pdf", stream));

            await EmailManager.ShowComposeNewEmailAsync(mail);
        }
    }

        public class NameValueItem
        {
            public string Name { get; set; }
            public double Value { get; set; }
        }

        public class DateValueItem
        {
            public DateTime Date { get; set; }
            public double Value { get; set; }
        }
}
